﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Microsoft.PointOfService;


namespace CA.RetailBeerSystems.POSServiceObjects.Scanners
{
	public class UsbReadThread : IDisposable
	{

		#region Fields

		// Fields
		internal NativeMethods._HIDD_ATTRIBUTES _Attributes = new NativeMethods._HIDD_ATTRIBUTES();
		private Microsoft.Win32.SafeHandles.SafeFileHandle _HidHandle;
		private System.IO.FileStream _UsbFileStream;
		private bool _Disposed;
		private int _InputReportByteLength;
		private Thread _ReadThread;
		private ResourceManager _ResourceManager = new ResourceManager("Strings", Assembly.GetExecutingAssembly());
		private string _SOName;
		private ManualResetEvent _ThreadStartedSignal;
		private WeakReference _WeakReferenceToScannerObject;

		#endregion

		#region Constructors / Finalisers

		public UsbReadThread()
		{
			this._ThreadStartedSignal = new ManualResetEvent(false);
		}  // END: public USBReadThread(HandHeldScanner Scanner)

		public UsbReadThread(HandHeldScanner Scanner)
		{
			this._SOName = Scanner.DeviceName;
			this._ThreadStartedSignal = new ManualResetEvent(false);
			this._WeakReferenceToScannerObject = new WeakReference(Scanner);
		}  // END: public USBReadThread(HandHeldScanner Scanner)

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		~UsbReadThread()
		{
			this.Dispose(false);
		}  // END: ~USBReadThread()

		#endregion

		#region Public Methods

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		public void CloseScanner()
		{
			if (_UsbFileStream != null)
			{
				_UsbFileStream.Dispose();
				_UsbFileStream = null;
			}

			if (_HidHandle != null)
				_HidHandle.Dispose();
		}  // END: public void CloseScanner()

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		public void Dispose()
		{
			try
			{
				this.Dispose(true);
			}
			finally
			{
				GC.SuppressFinalize(this);
			}
		}  // END: public void Dispose()

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		public void OpenScanner(string DevicePath)
		{
			try
			{
				if (this._Disposed)
					throw new ObjectDisposedException("USbReadThread");

				this.CloseScanner();

				_HidHandle = NativeMethods.CreateFile(DevicePath, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite, IntPtr.Zero, System.IO.FileMode.Open, NativeMethods.EFileAttributes.Overlapped, IntPtr.Zero);
				_UsbFileStream = new System.IO.FileStream(_HidHandle, System.IO.FileAccess.Read, 4096, true);

				IntPtr zero = IntPtr.Zero;
				if (NativeMethods.HidD_GetPreparsedData(_UsbFileStream.SafeFileHandle, ref zero) == 0)
				{
					Logger.Error(this._SOName, "Error reading HID preparsed data.");
					throw new PosControlException(this._ResourceManager.GetString("IDS_DEVICE_COMMUNICATION_FAILURE"), ErrorCode.Failure);
				}

				NativeMethods.HIDP_CAPS hidp_caps = new NativeMethods.HIDP_CAPS();
				int num = NativeMethods.HidP_GetCaps(zero, ref hidp_caps);
				if (num < 0)
				{
					Logger.Error(this._SOName, "Error reading HID capabilities: " + num.ToString(CultureInfo.InvariantCulture));
					throw new PosControlException(this._ResourceManager.GetString("IDS_DEVICE_COMMUNICATION_FAILURE"), ErrorCode.Failure);
				}

				this._InputReportByteLength = hidp_caps.InputReportByteLength;
				Logger.Info(this._SOName, "InputReportByteLength = " + this._InputReportByteLength.ToString(CultureInfo.InvariantCulture));

				this._ThreadStartedSignal.Reset();
				this._ReadThread = new Thread(new ThreadStart(this.ReadFromHidDevice));
				this._ReadThread.Name = "USB Thread: " + this._SOName;
				this._ReadThread.IsBackground = true;
				this._ReadThread.Start();
				if (!this._ThreadStartedSignal.WaitOne(0x7530, false))
				{
					Logger.Error(this._SOName, "USB read thread failed to start in 30 seconds.");
					throw new PosControlException(this._ResourceManager.GetString("IDS_THREAD_FAILED_TO_START"), ErrorCode.Failure);
				}

				Logger.Info(this._SOName, "USB read thread started successfully.");
				this.InitNativeMethodsAttributes();
			}
			catch (Exception exception)
			{
				Logger.Error(this._SOName, "An exception occurred while attempting to open the device.", exception);
				this.CloseScanner();
				throw;
			}

		}  // END: public void OpenScanner(string DevicePath)

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		protected virtual void Dispose(bool disposing)
		{
			if (!this._Disposed)
			{
				try
				{
					this.CloseScanner();
					if (disposing)
					{
						if (this._ThreadStartedSignal != null)
						{
							this._ThreadStartedSignal.Close();
							this._ThreadStartedSignal = null;
						}
					}
				}
				finally
				{
					this._Disposed = true;
				}
			}
		}  // END: protected virtual void Dispose(bool disposing)

		#endregion

		#region Private Methods

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		private void InitNativeMethodsAttributes()
		{
			this._Attributes.Size = (uint)Marshal.SizeOf(this._Attributes);
			if (_UsbFileStream != null)
				NativeMethods.HidD_GetAttributes(_UsbFileStream.SafeFileHandle, ref this._Attributes);
		}  // END: public void initNativeMethodsAttributes()

		private void ReadFromHidDevice()
		{
			try
			{
				if (this._ThreadStartedSignal != null)
					this._ThreadStartedSignal.Set();

				try
				{
					while (true)
					{
						byte[] buffer = new byte[this._InputReportByteLength];
						try
						{
							IAsyncResult result = _UsbFileStream.BeginRead(buffer, 0, this._InputReportByteLength, null, null);
							
							if (_UsbFileStream != null)
							{
								_UsbFileStream.EndRead(result);

								HandHeldScanner target = this._WeakReferenceToScannerObject.Target as HandHeldScanner;
								if (target != null)
									target.OnDataScanned(buffer);
							}
							else
								break; //Disposed/shutdown
						}
						catch (OperationCanceledException)
						{
							//Class is being disposed, device was removed etc.
							break;
						}
						catch (System.IO.IOException ioex)
						{
							//Device has been removed ?
							Logger.Error("UsbReadThread", "IO exception (device removed?) - " + ioex.Message);
						}
						catch (ObjectDisposedException)
						{
							//Shutting down
							break;
						}
					}
				}
				catch (Exception exception)
				{
					Logger.Error(this._SOName, "Exception occurred while reading data from the USB port.", exception);
					throw;
				}
			}
			catch (Exception exception2)
			{
				Logger.Error(this._SOName, "Exception occurred in USB read thread.", exception2);
				throw;
			}
		}  // END: private void ThreadMethod()

		#endregion

	}  // END: internal class UsbReadThread : IDisposable
}  // END: CA.RetailBeerSystems.POSServiceObjects.Scanners